"""
标准数据整合汇总模块
此模块主要用于实现将生成的一些标准数据进行整合，形成数据文件与列名、身份证号与数据文件及身份证号与列名之间的映射。方便后续关联查询。
使用方式：
    在DataSource的构造函数中，包函了对数据的整合、分析、映射文件的生成。故只需要生成此对象，即可完成相关工作。
"""

import os
import os.path
import pandas as pd
import numpy as np
from datatoolsobj import excel_out_file

class DataSource():
    """用于对指定目录下的所有标准数据进行整合，形成数据文件与文件列名映射、身份证号与数据文件映射，并存入文件中。
    """
    def __init__(self, dir_path="../标准数据", datamap="datamap", exts=['.feather']):
        """
        :param dir_path:指定标准数据文件所在目录
        :param datamap: 指定形成的映射文件保存文件夹，此文件夹保存于dir_path指定的目录中
        :param exts: 支持的标准数据文件的格式，当前只支持.feather格式文件
        """
        self.dir_path = dir_path
        self.exts = exts

        # 创建文件映射表目录
        self.datamap = datamap
        if not os.path.exists(os.path.join(dir_path, datamap)):
            os.mkdir(os.path.join(dir_path, datamap))

        # 获取所有数据文件
        self.files = []
        self.getDataFiles()  # 调用后self.files中为当前标准数据目录下的所有数据文件

        # 生成文件目录
        self.datamap_dir = os.path.join(dir_path, datamap)

        # 生成数据文件映射表
        self.map_df = pd.DataFrame()
        self.mapfilename = "数据文件映射表.xlsx"

        if not os.path.exists(os.path.join(self.datamap_dir, self.mapfilename)):
            # 不存在数据文件映射表，则创建文件
            self.createDataFileMap()
            self.saveDataFileMap()
            input("数据文件映射表已创建于{}，请按照需要进行修改。".format(os.path.join(self.datamap_dir, self.mapfilename)))
        else:
            # 若存在数据文件映射表，先进行读取，如果与当前标准数据中的数据文件相比有变动，则要求确认变动配置是否正确
            modify_file = self.reLoadDataFileMap()
            while modify_file:
                # 直到文件映射表与数据文件实际存在情况一致才结束循环
                self.saveDataFileMap()
                input("发现数据文件变动，请确认文件变动情况。")
                modify_file = self.reLoadDataFileMap()

        # 生成文件标识与文件列名映射表
        self.col_map_name = "文件列名映射表.feather"
        self.col_map_df = pd.DataFrame()
        self.getColumnsMap()
        self.saveColumnsMap()

        # 生成身份证号码与文件标识映射表
        self.id_map_name = "身份证号码与文件标识映射表.feather"
        self.id_map_df = pd.DataFrame()
        self.getDataIdMap()
        self.saveDataIdMap()

    def saveDataIdMap(self):
        """保存身份证号与文件标识映射表。"""
        self.id_map_df.to_feather(os.path.join(self.datamap_dir, self.id_map_name))

    def getDataIdMap(self):
        """获取身份证号与文件编号映射表"""
        # 获取数据文件映射表中所有标记是否使用为是的文件
        used_df = self.map_df.loc[self.map_df['是否使用'] == '是']
        # 生成身份证号与文件标识映射表头
        self.id_map_df = pd.DataFrame(columns=['身份证号码', "数据文件唯一标识", "备注"])
        index_count = 0
        for index, row in used_df.iterrows():
            # 遍历每个数据文件
            file_name = row['数据文件名']
            file_unique_id = row['数据文件唯一标识']
            combine_unique_id = row['数据整合唯一标识']
            data_full_path = row['数据文件完整路径']
            print(f"开始处理[{file_name}]文件", end="...")
            # 读取数据文件实际内容
            data = pd.read_feather(data_full_path)
            # 根据数据文件映射表中指出的数据整合唯一标识（即身份证号码），获取所有身份证数据
            data_id_values = data[combine_unique_id].values
            # 生成当前数据文件中所有身份证号与当前文件标识的映射表
            new_df = pd.DataFrame({"身份证号码": data_id_values, "数据文件唯一标识": file_unique_id, "备注": np.NaN})
            if new_df.empty:
                # 基本不可能存在
                raise ValueError(f"数据文件{file_name}中身份证号码及数据文件唯一标识都为空！")
            if self.id_map_df.empty:
                # 初次生成身份证号与数据文件标识映射表
                self.id_map_df = new_df
            else:
                # 再次生成身份证号与数据文件标识映射表，需要加入到之前生成的数据映射表之后
                self.id_map_df = pd.concat([self.id_map_df, new_df], ignore_index=True)
            print("完成。")
        # 去掉所有重复的映射关系
        self.id_map_df = self.id_map_df.drop_duplicates()

    def getColumnsMap(self):
        """获取文件列名映射表。"""
        # 获取数据文件映射表中所有标记是否使用为是的文件
        used_df = self.map_df.loc[self.map_df['是否使用'] == '是']
        # 生成列名与文件标识映射表头
        self.col_map_df = pd.DataFrame(columns=['数据文件唯一标识', "列名", "备注"])
        index_count = 0
        for index, row in used_df.iterrows():
            # 遍历每个数据文件
            file_unique_id = row['数据文件唯一标识']
            combine_unique_id = row['数据整合唯一标识']
            data_full_path = row['数据文件完整路径']
            # 获取指定数据文件的列标题
            columns_list = self.getColNames(data_full_path)
            for column in columns_list:
                # 遍历所有列名，标记‘身份证号’及‘身份证号码’列为数据合并唯一标识列
                if column == combine_unique_id:
                    commont = "数据整合唯一标识"
                else:
                    commont = np.NaN
                self.col_map_df.loc[index_count, :] = [file_unique_id, column, commont]
                index_count += 1

    def saveColumnsMap(self):
        """保存文件列名与文件标识映射表"""
        self.col_map_df.to_feather(os.path.join(self.datamap_dir, self.col_map_name))

    def saveDataFileMap(self):
        """保存数据文件映射表"""
        mapfile = os.path.join(self.datamap_dir, self.mapfilename)
        excel_out_file(self.map_df, mapfile, "数据文件映射表", "数据文件映射表")

    def reLoadDataFileMap(self):
        """重新加载数据文件映射表
        如果数据文件发生了变动（增加或删除了数据文件），则返回True，否则返回False
        """
        # 生成数据文件映射表路径
        mapfile = os.path.join(self.datamap_dir, self.mapfilename)
        # 重新读取文件内容
        self.map_df = pd.read_excel(mapfile, header=1)
        # 获取数据文件名列表
        old_files = self.map_df['数据文件名'].values.tolist()

        modify_file = False
        new_files = []
        # 获取当前标准数据文件夹下所有数据文件
        self.getDataFiles()

        # 遍历所有实际存在的文件，发现变动的文件
        for file in self.files:
            name = file.split('.')[0]
            if name in old_files:
                old_files.remove(name)
            else:
                # 数据文件目录中新增了数据，需要新编码映射
                new_files.append(file)

        # old_files不为空，则表示数据文件中删除了数据
        if len(old_files) > 0:
            modify_file = True
            self.map_df = self.map_df.drop(self.map_df.loc[self.map_df['数据文件名'].isin(old_files)].index)

        # new_files不为空，则表示新增了数据
        if len(new_files) > 0:
            modify_file = True
            last_file_id = self.map_df.iloc[-1, 1]
            #print(last_file_id)
            for file in new_files:
                name = file.split('.')[0]
                full_path = os.path.abspath(os.path.join(self.dir_path, file))
                columns_list = self.getColNames(full_path)
                combine_unique_id = np.NaN
                use = "否"
                if '身份证号' in columns_list:
                    combine_unique_id = '身份证号'
                    use = "是"
                if '身份证号码' in columns_list:
                    combine_unique_id = '身份证号码'
                    use = "是"
                row = [self.map_df.shape[0] + 1, last_file_id + 1, name, combine_unique_id, full_path, use, np.NaN]
                self.map_df.loc[self.map_df.shape[0]] = row
                last_file_id += 1

        return modify_file

    def createDataFileMap(self):
        """
        生成数据文件映射表，将数据文件编号，指定数据文件的整合标识列。
        :return: 数据映射表
        """
        unique_count = 10001
        index_count = 0
        self.map_df = pd.DataFrame(
            columns=['序号', '数据文件唯一标识', '数据文件名', '数据整合唯一标识', '数据文件完整路径', '是否使用',
                     '备注'])
        for file in self.files:
            name = file.split('.')[0]
            full_path = os.path.abspath(os.path.join(self.dir_path, file))
            columns_list = self.getColNames(os.path.join(self.dir_path, file))
            combine_unique_id = np.NaN
            use = "否"
            if '身份证号' in columns_list:
                combine_unique_id = '身份证号'
                use = "是"
            if '身份证号码' in columns_list:
                combine_unique_id = '身份证号码'
                use = "是"
            row = [index_count + 1, unique_count + index_count, name, combine_unique_id, full_path, use, np.NaN]
            self.map_df.loc[index_count, :] = row
            index_count += 1

    def getColNames(self, data_path):
        """获取指定数据文件的列标题"""
        data = pd.read_feather(data_path)
        return data.columns.tolist()

    def getDataFiles(self):
        '''获取指定目录下的所有文件'''
        self.files = []
        for file_name in os.listdir(self.dir_path):
            file_name_ext = os.path.splitext(file_name)
            if file_name_ext[-1] not in self.exts:
                continue
            self.files.append(file_name)

if __name__ == '__main__':
    ds = DataSource()